package kitwee.com.kanpad.orderDetail;

import android.content.Context;
import android.os.CountDownTimer;

import com.blankj.utilcode.util.LogUtils;
import com.kitwee.serialport.data.SerialPortDatabase;
import com.kitwee.serialport.data.model.SerialPortData;
import com.kitwee.serialport.util.SLog;
import com.kitwee.serialport.util.SimpleCountDownTimer;
import com.kitwee.serialport.util.Times;

import com.zhy.http.okhttp.OkHttpUtils;
import com.zhy.http.okhttp.callback.StringCallback;

import org.apache.commons.codec.binary.Hex;
import org.apache.commons.codec.digest.DigestUtils;
import org.json.JSONException;
import org.json.JSONObject;

import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import kitwee.com.kanpad.BuildConfig;
import okhttp3.Call;
import utils.DeviceUtils;
import utils.FlavorHelper;
import utils.LinkedHashSetBlockingQueue;
import utils.SPUtils;

public final class SerialPortReporter {

    private String mUserKey;
    private String mUserName;
    private String mDeviceNo;

    private String endPoint;
    private String CONNECT;
    private String DISCONNECT;
    private String UPLOAD;

    private volatile SerialPortData mCurrData = null;
    // 产量（为累加值）
    private double mLastOutput = 0;
    private double mLastMinuteOutput = 0;
    // 暂停时备份产量
    private double mLastMinuteOutputBak = 0;
    // 时间戳（统计机器运行时长）
    private Date mLastTimestamp = null;
    private Date mLastMinuteTimestamp = null;

    private LinkedHashSetBlockingQueue<SerialPortData> mRetransmissionQueue;
    private static final int MAX_QUEUE_LENGTH = 1440;

    private RetransmissionThread mRetransmissionThread;

    private Timer mIntervalTimer;
    private CountDownTimer mDataTimeout;
    // 存储数据上报时间间隔(单位：秒)
    private static final int PERSISTENCE_PERIOD = 60;

    // 重传数据库操作
    private ExecutorService mDatabaseService;
    private SerialPortDatabase mDatabase;

    private boolean mOffline = true;

    private static final SerialPortReporter INSTANCE = new SerialPortReporter();

    private SerialPortReporter() {
    }

    public static SerialPortReporter getInstance() {
        return INSTANCE;
    }

    /**
     * 初始化上传服务
     */
    public void initialize(Context context) {
//        if (FlavorHelper.isGoertek()) {
//            mUserName = SPUtils.getString(context, "user");
//            mUserKey = SPUtils.getString(context, "key");
//            endPoint = BuildConfig.API_GOERTEK_UART;
//            CONNECT = "/device/deviceConnect";
//            DISCONNECT = "/device/deviceDisConnect";
//            UPLOAD = "/device/deviceDataUpload";
//        } else {
        mUserName = "kitwee";
        mUserKey = SPUtils.getString(context, "SerialPortKey");
        endPoint = BuildConfig.API_SERIAL_PORT_URL;
        CONNECT = "/connect";
        DISCONNECT = "/disconnect";
        UPLOAD = "/upload";
//        }

        mDeviceNo = DeviceUtils.getPadionId();

        mDatabase = SerialPortDatabase.getInstance(context);

        initializeTimer();

        mRetransmissionQueue = new LinkedHashSetBlockingQueue<>(MAX_QUEUE_LENGTH);
        mRetransmissionThread = new RetransmissionThread();
        mRetransmissionThread.start();

        mDatabaseService = Executors.newSingleThreadExecutor();
    }

    private void initializeTimer() {
        mIntervalTimer = new Timer(true);
        mIntervalTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
//                reportPersistentData();
            }
        }, PERSISTENCE_PERIOD * 1000, PERSISTENCE_PERIOD * 1000); // 存储数据上报

        mDataTimeout = new SimpleCountDownTimer(60_000) {
            @Override
            public void onFinish() {
                mCurrData = null;
                LogUtils.e("SimpleCountDownTimer onFinish");
                reportEquipmentStatus(Times.currentDateString(), mOffline = true);
            }
        }; // 60秒内，若实时数据未更新，则判定设备离线

        mIntervalTimer.scheduleAtFixedRate(new TimerTask() {
            @Override
            public void run() {
                scanSerialPortDatabase();
                LogUtils.e("扫描重传数据库");
            }
        }, 3_000, 15 * 60 * 1000); // 扫描重传数据库
    }

    /**
     * 上报存储数据
     */
    private void reportPersistentData() {
        reportData(mCurrData, true, false);
    }

    /**
     * 上报实时数据
     */
    public void reportRealTimeData(boolean paused, SerialPortData data) {
        LogUtils.e("reportRealTimeData" + data);
        if (paused) {
            mLastTimestamp = null;
            mLastMinuteTimestamp = null;
        }
        if (mOffline) {
//            reportEquipmentStatus(data.getFlag(), mOffline = false);
        } // 通知设备已上线

        mDataTimeout.cancel();
        if (paused) {
            mCurrData = data; // 将实时数据替换为暂停值
        } else {
            // TODO: 2019/3/2   数据上报关闭

//            reportData(data, false, false);
        }
        mDataTimeout.start();
    }

    /**
     * 暂停状态发生变更
     * 此时需要备份或恢复存库产量数据
     */
    public void onPausedStatusChanged(boolean paused) {
        if (paused) { // 备份上一次的产量
            mLastMinuteOutputBak = mLastMinuteOutput;
        } else { // 恢复上一次的产量
            mLastMinuteOutput = mLastMinuteOutputBak;
        }
    }

    /**
     * 上报串口数据
     *
     * @param data    数据
     * @param persist 是否存储
     */
    private synchronized void reportData(final SerialPortData data, final boolean persist, boolean retransmission) {
        if (data == null) {
            return;
        }
        if (!retransmission && !persist) {
            mCurrData = data; // 实时数据
        }
        Map<String, String> params = buildSerialPortDataParams(data, persist);
        OkHttpUtils.get().url(endPoint + UPLOAD).params(correctParams(data, params)).build().execute(new StringCallback() {
            @Override
            public void onError(Call call, Exception e, int id) {
                onReportDataFailure(data, persist);
            }

            @Override
            public void onResponse(String response, int id) {
                if (isResponseOk(response)) {
                    onReportDataSuccess(data, persist);
                } else {
                    onReportDataFailure(data, persist);
                }
            }
        });
    }

    /**
     * 修正参数
     *
     * @param data   数据
     * @param params 参数
     */
    private Map<String, String> correctParams(SerialPortData data, Map<String, String> params) {
        if (!data.isReported()) { // 重传数据
            double output = Double.parseDouble(params.get("standard_output"));
            double runningSeconds = 0;
            if (output > 0) {
                runningSeconds = PERSISTENCE_PERIOD;
            }
            params.put("standard_runsecond", String.valueOf(runningSeconds));
        }
        return params;
    }

    /**
     * 重传数据
     */
    private void retransmissionData(SerialPortData data) {
        reportData(data, true, true);
    }

    /**
     * 数据上报成功
     *
     * @param data    数据
     * @param persist 是否存储
     */
    private void onReportDataSuccess(final SerialPortData data, boolean persist) {
        if (persist) {
            if (!data.isReported()) {
                submitDatabaseTask(new Runnable() {
                    @Override
                    public void run() {
                        mDatabase.serialPortDao().delete(data);
                    }
                });
                SLog.d("再次上报数据最终成功");
            }
        } else {
            SLog.d("实时数据上报成功~");
        }
    }

    /**
     * 数据上报失败
     *
     * @param data    数据
     * @param persist 是否存储
     */
    private void onReportDataFailure(final SerialPortData data, boolean persist) {
        if (persist) {
            if (data.isReported()) { // 首次上报失败
                submitDatabaseTask(new Runnable() {
                    @Override
                    public void run() {
                        SLog.d("保存重传数据到数据库：" + data.getFlag());
                        mDatabase.serialPortDao().insert(data);
                    }
                });
                data.setReported(false);

            } else {
                SLog.d("再次上报仍旧失败……" + data.getFlag());
            }

            if (!mRetransmissionQueue.contains(data) &&
                    mRetransmissionQueue.remainingCapacity() > 0) {
                if (mRetransmissionQueue.offer(data)) {
                    SLog.e("发送失败，已添加到重传队列" + mRetransmissionQueue.size());
                } else {
                    SLog.e("发送失败，且未能添加到重传队列");
                }
            }

        } else {
            SLog.e("实时数据上报失败！");
        }
    }

    private void submitDatabaseTask(Runnable runnable) {
        if (mDatabaseService.isShutdown()) {
            return;
        }
        mDatabaseService.submit(runnable);
    }

    /**
     * 上报串口数据参数
     */
    private Map<String, String> buildSerialPortDataParams(SerialPortData data, boolean persist) {
        Map<String, String> params = buildGeneralParams(data.getFlag());
        params.put("device_data", data.getData());
        params.put("db_store", persist ? "1" : "0");
        try {
            JSONObject object = new JSONObject(data.getData());

            //标准产量
            double currOutput = object.optDouble("DT32761", 0.0);
            double standardOutput = handleOutput(persist, currOutput);
            params.put("standard_output", String.valueOf(standardOutput < 0 ? 0 : standardOutput));

            // 标准速度
            params.put("standard_speed", String.valueOf(object.optInt("DT214")));

            // 运行时长
            Date tempTimestamp = Times.parseDateString(data.getFlag());
            double runningSeconds = handleRunningSeconds(persist, standardOutput, tempTimestamp);
            params.put("standard_runsecond", String.valueOf(runningSeconds));

        } catch (JSONException e) {
            e.printStackTrace();
        }
        return params;
    }

    /**
     * 处理产量数据（取差值）
     *
     * @param persist    是否需要存库
     * @param currOutput 当前数据的产量
     */
    private double handleOutput(boolean persist, double currOutput) {
        double standardOutput;
        if (persist) {
            standardOutput = currOutput - mLastMinuteOutput;
            mLastMinuteOutput = currOutput;
        } else {
            standardOutput = currOutput - mLastOutput;
            mLastOutput = currOutput;
        }
        return standardOutput;
    }

    /**
     * 处理运行时长
     *
     * @param persist       是否持久化
     * @param output        产量（差值）
     * @param currTimestamp 当前数据的时间戳
     */
    private double handleRunningSeconds(boolean persist, double output, Date currTimestamp) {
        double runningSeconds;
        if (output <= 0) {
            if (persist) {
                mLastMinuteTimestamp = null;
            } else {
                mLastTimestamp = null;
            }
            runningSeconds = 0;

        } else if (persist) {
            if (mLastMinuteTimestamp == null) {
                runningSeconds = PERSISTENCE_PERIOD;
            } else {
                runningSeconds = calculateDateDuration(currTimestamp, mLastMinuteTimestamp);
            }
            mLastMinuteTimestamp = currTimestamp;

        } else {
            if (mLastTimestamp == null) {
                runningSeconds = 2; // 采集一次数据的时间
            } else {
                runningSeconds = calculateDateDuration(currTimestamp, mLastTimestamp);
            }
            mLastTimestamp = currTimestamp;
        }
        return runningSeconds;
    }

    /**
     * 计算时间间隔
     *
     * @param curr 当前日期
     * @param pre  前一个日期
     */
    private double calculateDateDuration(Date curr, Date pre) {
        return (curr.getTime() - pre.getTime()) / 1000;
    }

    /**
     * 上报设备状态参数
     */
    private Map<String, String> buildEquipmentStatusParams(String uploadTime) {
        return buildGeneralParams(uploadTime);
    }


    /**
     * 接口请求通用参数
     *
     * @param uploadTime 上报时间
     */
    private Map<String, String> buildGeneralParams(String uploadTime) {
        HashMap<String, String> params = new HashMap<>();

        long timestamp = System.currentTimeMillis();
        params.put("t", String.valueOf(timestamp));
        params.put("pd", generateDigest(timestamp));

        params.put("pun", mUserName);
        params.put("device_no", mDeviceNo);
        params.put("upload_time", uploadTime);
        return params;
    }


    /**
     * 生成消息摘要
     *
     * @param timestamp 时间戳
     */
    private String generateDigest(long timestamp) {
        return new String(Hex.encodeHex(DigestUtils.md5((mUserKey + mUserName + timestamp).getBytes())));
    }

    /**
     * 重传线程
     */
    private class RetransmissionThread extends Thread {

        @Override
        public void run() {
            while (!isInterrupted()) {
                retransmissionData(mRetransmissionQueue.poll());
                try {
                    sleep(1_500); // 1.5秒之后重传下一条数据
                } catch (InterruptedException e) {
                    // do nothing
                }
            }
        }
    }

    /**
     * 扫描数据库以重传
     */
    private void scanSerialPortDatabase() {
        if (mRetransmissionQueue == null) {
            return;
        }
        int rows = mRetransmissionQueue.remainingCapacity() / 2;
        if (rows > 0) {
            List<SerialPortData> failedData = mDatabase.serialPortDao().query(rows);
            if (failedData == null || failedData.isEmpty()) {
                SLog.d("没有查询到需要重传的数据");
                return;
            }
            SerialPortData tempData;
            for (int i = 0; i < failedData.size(); i++) {
                tempData = failedData.get(i);
                if (mRetransmissionQueue.contains(tempData)) {
                    continue;
                }
                tempData.setReported(false);
                mRetransmissionQueue.add(tempData);
            }

        } else {
            SLog.d("重传队列似乎已经排满了!");
        }
    }

    /**
     * 上报设备状态
     *
     * @param offline 是否下线
     */
    private void reportEquipmentStatus(String uploadTime, boolean offline) {
        OkHttpUtils.get().url(endPoint.concat(offline ? DISCONNECT : CONNECT)).params(buildEquipmentStatusParams(uploadTime)).build().execute(new StringCallback() {
            @Override
            public void onError(Call call, Exception e, int id) {
                SLog.e("设备状态上报失败！");
            }

            @Override
            public void onResponse(String response, int id) {
                if (isResponseOk(response)) {
                    SLog.d("设备状态上报成功~");
                } else {
                    SLog.e("设备状态上报出错！");
                }
            }
        });
    }

    /**
     * 判断接口是否请求成功
     *
     * @param response 响应
     */
    private boolean isResponseOk(String response) {
        try {
            JSONObject object = new JSONObject(response);
            return object.optBoolean("status");

        } catch (JSONException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 取消初始化上传服务
     */
    public void deinitialize() {
        if (mIntervalTimer != null) {
            mIntervalTimer.cancel();
        }
        if (mRetransmissionThread != null) {
            mRetransmissionThread.interrupt();
        }

        if (mDatabaseService != null) {
            mDatabaseService.shutdownNow();
        }
    }

}
